package com.sky.aspect;

import com.github.benmanes.caffeine.cache.Cache;
import com.sky.annotation.HotDataCache;
import com.sky.exception.BusinessException;
import com.sky.task.HotStatisticsTask;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

@Aspect
@Component
public class HotDataCacheAspect {

    @Autowired
    private HotStatisticsTask hotStatisticsTask;

    @Autowired
    private CacheManager cacheManager;

    /**
     * 锁信息包装类（优化：增加 volatile 保证 lastUsedTime 可见性）
     */
    private static class LockInfo {
        ReentrantLock lock;
        volatile long lastUsedTime; // 最后使用时间（毫秒）

        LockInfo(ReentrantLock lock) {
            this.lock = lock;
            this.lastUsedTime = System.currentTimeMillis();
        }
    }

    /**
     * 锁池：存储"锁Key→锁信息"的映射
     */
    private static final Map<String, LockInfo> lockPool = new ConcurrentHashMap<>();

    /**
     * 套餐缓存名称集合（明确匹配，避免字符串包含误判）
     */
    private static final Set<String> SETMEAL_CACHE_NAMES = new HashSet<>(Arrays.asList(
            "setmeal_cache",
            "hot_setmeal_cache",
            "setmeal_detail_cache"
    ));


    /**
     * 锁清理线程池（自定义配置，避免 OOM）
     */
    private static final ScheduledExecutorService lockCleaner = new ScheduledThreadPoolExecutor(
            1,
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(1);
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "hot-cache-lock-cleaner-" + counter.getAndIncrement());
                    thread.setDaemon(true); // 守护线程，随 JVM 退出
                    return thread;
                }
            },
            new ThreadPoolExecutor.DiscardOldestPolicy()
    );

    static {
        // 锁清理任务：每10分钟清理10分钟未使用的锁
        lockCleaner.scheduleAtFixedRate(() -> {
            long now = System.currentTimeMillis();
            Iterator<Map.Entry<String, LockInfo>> iterator = lockPool.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, LockInfo> entry = iterator.next();
                if (now - entry.getValue().lastUsedTime > 10 * 60 * 1000) {
                    iterator.remove();
                }
            }
        }, 10, 10, TimeUnit.MINUTES);
    }

    @Pointcut("@annotation(com.sky.annotation.HotDataCache)")
    public void hotDataCachePointcut() {}

    @Around("hotDataCachePointcut() && @annotation(hotDataCache)")
    public Object doHotDataCache(ProceedingJoinPoint joinPoint, HotDataCache hotDataCache) throws Throwable {
        return doHotDataCacheWithRetry(joinPoint, hotDataCache, 0);
    }

    private Object doHotDataCacheWithRetry(ProceedingJoinPoint joinPoint, HotDataCache hotDataCache, int retryCount) throws Throwable {
        // 1. 解析参数：获取缓存名称和业务ID
        String cacheName = hotDataCache.cacheName();
        Object[] methodArgs = joinPoint.getArgs();
        if (methodArgs == null || methodArgs.length == 0) {
            return joinPoint.proceed();
        }
        Object businessId = methodArgs[0];

        // 确保业务ID是Long类型
        if (!(businessId instanceof Long)) {
            return joinPoint.proceed();
        }
        Long id = (Long) businessId;

        // 2. 获取缓存实例（使用 Spring Cache 抽象，解耦 Caffeine）
        org.springframework.cache.Cache cache = cacheManager.getCache(cacheName);
        if (cache == null) {
            return joinPoint.proceed();
        }

        // 3. 第一次查询缓存（快速路径）
        org.springframework.cache.Cache.ValueWrapper wrapper = cache.get(id);
        if (wrapper != null) {
            return wrapper.get();
        }

        // 4. 判断是否为热门数据
        boolean isHot = isHotData(id, cacheName);

        // 5. 根据热门程度处理
        if (isHot) {
            return handleHotData(joinPoint, hotDataCache, retryCount, id, cacheName, cache);
        } else {
            return handleNormalData(joinPoint, id, cache);
        }
    }

    /**
     * 判断是否为热门数据（优化：明确匹配缓存名称）
     */
    private boolean isHotData(Long id, String cacheName) {
        if (SETMEAL_CACHE_NAMES.contains(cacheName)) {
            return hotStatisticsTask.isHotSetmeal(id);
        } else {
            return hotStatisticsTask.isHotDish(id);
        }
    }

    /**
     * 处理热门数据：加锁防止缓存击穿（优化：使用注解重试参数）
     */
    private Object handleHotData(ProceedingJoinPoint joinPoint, HotDataCache hotDataCache,
                                 int retryCount, Long id, String cacheName,
                                 org.springframework.cache.Cache cache) throws Throwable {
        String lockKey = cacheName + ":" + id;

        // 获取锁并更新使用时间
        LockInfo lockInfo = lockPool.computeIfAbsent(lockKey, k -> new LockInfo(new ReentrantLock()));
        lockInfo.lastUsedTime = System.currentTimeMillis();
        ReentrantLock lock = lockInfo.lock;

        try {
            // 尝试获取锁（最多等待注解配置的时间）
            boolean isLocked = lock.tryLock(hotDataCache.lockWaitTime(), TimeUnit.SECONDS);
            if (isLocked) {
                try {
                    // 二次校验缓存
                    org.springframework.cache.Cache.ValueWrapper wrapper = cache.get(id);
                    if (wrapper != null) {
                        return wrapper.get();
                    }

                    // 执行目标方法（查询数据库）
                    Object dbValue;
                    try {
                        dbValue = joinPoint.proceed();
                    } catch (BusinessException e) {
                        throw e; // 业务异常不重试
                    } catch (Exception e) {
                        // 非业务异常：使用注解配置的重试参数
                        if (retryCount >= hotDataCache.maxRetryCount()) {
                            throw new RuntimeException("查询失败，已达最大重试次数", e);
                        } else {
                            TimeUnit.MILLISECONDS.sleep(hotDataCache.retryIntervalMs());
                            return doHotDataCacheWithRetry(joinPoint, hotDataCache, retryCount + 1);
                        }
                    }

                    // 写入缓存（空值依赖 Caffeine 配置的过期时间）
                    cache.put(id, dbValue);
                    return dbValue;
                } finally {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            } else {
                // 加锁失败：使用注解配置的重试参数
                if (retryCount >= hotDataCache.maxRetryCount()) {
                    throw new RuntimeException("当前访问人数过多，请稍后重试");
                }
                TimeUnit.MILLISECONDS.sleep(hotDataCache.retryIntervalMs());
                return doHotDataCacheWithRetry(joinPoint, hotDataCache, retryCount + 1);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("查询被中断，请重试");
        }
    }

    /**
     * 处理普通数据：简单缓存逻辑（优化：删除空值清理线程）
     */
    private Object handleNormalData(ProceedingJoinPoint joinPoint, Long id,
                                    org.springframework.cache.Cache cache) throws Throwable {
        Object dbValue = joinPoint.proceed();
        cache.put(id, dbValue); // 空值依赖 Caffeine 配置的 5 分钟过期
        return dbValue;
    }

    /**
     * 资源清理：优雅关闭线程池（优化：等待线程池终止）
     */
    @PreDestroy
    public void shutdown() {
        // 关闭锁清理线程池
        lockCleaner.shutdown();
        try {
            if (!lockCleaner.awaitTermination(5, TimeUnit.SECONDS)) {
                lockCleaner.shutdownNow(); // 强制关闭未终止的线程
            }
        } catch (InterruptedException e) {
            lockCleaner.shutdownNow();
        }
    }
}
